package com.jms.framework.jdbc.context;

import com.jms.framework.config.JmsConfig;
import com.jms.framework.config.JmsConfigItem;
import com.jms.framework.context.ApplicationContext;
import com.jms.framework.exception.PropertyAlreadySetException;
import com.jms.framework.jdbc.DataSourceManagement;
import com.jms.framework.jdbc.transaction.TransactionManagement;
import com.jms.framework.jdbc.transaction.adaper.TransactionManagementAdapter;
import com.jms.framework.jdbc.transaction.annotation.EnableTransactionManagement;
import com.jms.framework.jdbc.transaction.exception.TransactionException;
import com.mysql.cj.jdbc.MysqlDataSource;
import com.zaxxer.hikari.HikariDataSource;

import javax.sql.DataSource;

/**
 * 抽象的JDBC上下文类，实现了JDBCContext接口
 * 提供了数据源的初始化和销毁方法，以及事务管理的相关逻辑
 */
public abstract class AbstractJDBCContext implements JDBCContext {

    // 应用上下文
    protected ApplicationContext applicationContext;

    protected boolean initialized = false;

    /**
     * 设置应用上下文
     *
     * @param applicationContext 应用上下文
     * @throws PropertyAlreadySetException 如果已经设置了应用上下文，则抛出此异常
     */
    public void setApplicationContext(ApplicationContext applicationContext) {
        if (this.applicationContext != null) {
            throw new PropertyAlreadySetException(ApplicationContext.class, "applicationContext");
        }
        this.applicationContext = applicationContext;
        JmsConfig jmsConfig = applicationContext.getJmsConfig();
        if ("true".equals(jmsConfig.getAttribute(JmsConfigItem.ENABLE_TRANSACTION_MANAGEMENT.getValue()))) {
            TransactionManagementAdapter.openTransactionManagement();
        }
    }

    /**
     * 初始化方法
     * 加载数据源，并根据配置决定是否开启事务管理
     */
    @Override
    @SuppressWarnings("unchecked")
    public void init() {
        if (initialized) {
            throw new IllegalStateException("the jdbc context has been initialized.");
        }
        //配置全局唯一数据源
        DataSource dataSource = applicationContext.getBean(DataSource.class);
        if (dataSource == null) {
            dataSource = loadDataSource();
        }
        DataSourceManagement.getInstance().loadDataSource(dataSource);
        initialized = true;

        JmsConfig jmsConfig = applicationContext.getJmsConfig();
        boolean enableTransactionManagement = false;
        String transactionManagementClassName = null;
        if (jmsConfig.getBootClass().isAnnotationPresent(EnableTransactionManagement.class)) {
            EnableTransactionManagement enable = jmsConfig.getBootClass().getAnnotation(EnableTransactionManagement.class);
            enableTransactionManagement = enable.value();
            transactionManagementClassName = enable.managementClass();
        } else if ("true".equals(jmsConfig.getAttribute(JmsConfigItem.ENABLE_TRANSACTION_MANAGEMENT.getValue()))) {
            enableTransactionManagement = true;
            transactionManagementClassName = jmsConfig.getAttribute(JmsConfigItem.TRANSACTION_MANAGEMENT_CLASS.getValue());
        }
        if (enableTransactionManagement) {
            if (transactionManagementClassName == null) {
                transactionManagementClassName = "com.jms.framework.jdbc.transaction.GeneralTransactionManagement";
            }
            try {
                Class<? extends TransactionManagement> transactionManagementClass = (Class<? extends TransactionManagement>) Class.forName(transactionManagementClassName);
                TransactionManagement transactionManagement = applicationContext.getBean(transactionManagementClass);
                if (transactionManagement == null) {
                    transactionManagement = transactionManagementClass.newInstance();
                }
                TransactionManagementAdapter.getInstance().setTransactionManagement(transactionManagement);
            } catch (ClassNotFoundException e) {
                throw new TransactionException("not found the transaction management class:" + transactionManagementClassName, e);
            } catch (InstantiationException| IllegalAccessException e) {
                throw new TransactionException("create the transaction management class instance error:", e);
            }
        }
    }

    /**
     * 销毁方法
     * 释放数据源和应用上下文
     */
    @Override
    public void destroy() {
        if (applicationContext != null) {
            applicationContext = null;
        }
    }

    /**
     * 获取数据源
     *
     * @return 数据源实例
     */
    @Override
    public DataSource getDataSource() {
        return DataSourceManagement.getInstance().getDataSource();
    }

    /**
     * 检查事务管理是否开启
     *
     * @return 如果事务管理已开启，返回true；否则返回false
     */
    @Override
    public boolean transactionManagementIsOpen() {
        return TransactionManagementAdapter.transactionManagementIsOpen();
    }

    /**
     * 获取事务管理实例
     *
     * @return 事务管理实例
     */
    @Override
    public TransactionManagement getTransactionManagement() {
        return TransactionManagementAdapter.getInstance().getTransactionManagement();
    }

    /**
     * 加载数据源
     * 根据配置创建不同类型的数据源实例
     */
    private DataSource loadDataSource() {
        DataSource dataSource;
        JmsConfig config = applicationContext.getJmsConfig();
        // 从配置属性中获取数据库连接的必要信息
        String driver = config.getAttribute(JmsConfigItem.JMS_DATASOURCE_DRIVER.getValue());
        String url = config.getAttribute(JmsConfigItem.JMS_DATASOURCE_URL.getValue());
        String username = config.getAttribute(JmsConfigItem.JMS_DATASOURCE_USERNAME.getValue());
        String password = config.getAttribute(JmsConfigItem.JMS_DATASOURCE_PASSWORD.getValue());
        String type =  config.getAttribute(JmsConfigItem.JMS_DATASOURCE_TYPE.getValue());
        // 根据数据库类型创建相应的DataSource对象
        if ("mysql".equals(type)) {
            MysqlDataSource mysqlDataSource = new MysqlDataSource();
            mysqlDataSource.setUrl(url);
            mysqlDataSource.setUser(username);
            mysqlDataSource.setPassword(password);
            dataSource = mysqlDataSource;
        } else {
            HikariDataSource hikariDataSource = new HikariDataSource();
            hikariDataSource.setDriverClassName(driver);
            hikariDataSource.setJdbcUrl(url);
            hikariDataSource.setUsername(username);
            hikariDataSource.setPassword(password);
            dataSource = hikariDataSource;
        }
        return dataSource;
    }

}
